package br.org.mestrado.vais;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

import br.org.mestrado.vais.comparator.ParetoMin;
import br.org.mestrado.vais.util.LEGENDA;
import br.org.mestrado.vais.util.PROBLEMA;

public class VAIS {

	final int numObjectives;

	final int numDecisionsVar;

	// critério de parada é o nº de fitness call
	// ou número de gerações
	final int stopCriteria;

	// total de inner loops
	final int innerLoops;

	// contador de runs
	final int runs;

	// amplitude inicial da muta��o
	double startAmplitude;

	// população inicial e máxima
	final int nPop;

	// tamanho nova população inicial e máxima
	final int nNewPop;

	// máximo de Ag na memória
	final int nMaxMem;

	// total de clones por Ag
	final int nClones;

	// problema escolhido
	final PROBLEMA problema;

	// incrementador único de ID de clone
	final AtomicInteger atomicInteger;

	// tipo de avaliação pareto a ser instanciada
	final ParetoMin pareto;

	// limiar de supressão
	final double thresholdSupression;

	private Logger logger = Logger.getLogger(VAIS.class.getPackage().getName());

	public VAIS(int nUMOBJECTIVES, int nUMDECISIONVAR, int sTOPCRTERIA,
			int iNNERLOOPS, int rUNS, double sTARTAMPLITUDE, int nPOP,
			int nMAXMEN, int nCLONES, PROBLEMA problema,
			AtomicInteger atomicInteger, ParetoMin pareto) {

		super();

		numObjectives = nUMOBJECTIVES;

		numDecisionsVar = nUMDECISIONVAR;

		stopCriteria = sTOPCRTERIA;

		innerLoops = iNNERLOOPS;

		runs = rUNS;

		startAmplitude = sTARTAMPLITUDE;

		nPop = nPOP;

		nNewPop = Double.valueOf(nPOP * 0.2).intValue();

		nMaxMem = nMAXMEN;

		nClones = nCLONES;

		this.problema = problema;

		this.atomicInteger = atomicInteger;

		this.pareto = pareto;

		this.thresholdSupression = Math.sqrt(Double.valueOf(numObjectives))
				/ Double.valueOf(nMaxMem);
	}

	List<Anticorpo> generatePopulation(double porcentage) {

		final List<Anticorpo> pop = new ArrayList<Anticorpo>();

		int var = (int) porcentage;

		do {

			for (int i = 0; i < var; i++) {

				pop.add(new Anticorpo(i, numObjectives, numDecisionsVar,
						nClones, nClones, startAmplitude, problema,
						atomicInteger, nPop));

			}

			for (Anticorpo anticorpo : pop) {
				// calcula os objetivos
				anticorpo.calculateObjectives(true);
			}

			for (Iterator<Anticorpo> iterator = pop.iterator(); iterator
					.hasNext();) {
				Anticorpo anticorpo = iterator.next();
				if (!problema.isConstrained(anticorpo.getDecisionVariables()))
					iterator.remove();
			}

			var = (int) (porcentage - pop.size());

		} while (var > 0);

		return pop;
	}

	void masterReset(final List<Anticorpo> pop) {

		double MAX_FITNESS = Double.MIN_VALUE;

		double MIN_FITNESS = Double.MAX_VALUE;

		double MAX_OMEGA = Double.MIN_VALUE;

		double MIN_OMEGA = Double.MAX_VALUE;

		// zera o geral
		Anticorpo.setMaxFitness(Double.MIN_VALUE);

		Anticorpo.setMinFitness(Double.MAX_VALUE);

		Anticorpo.setMaxOmega(Double.MIN_VALUE);

		Anticorpo.setMinOmega(Double.MAX_VALUE);

		// reseta população.
		// próxima iteração terá os valores atualizados

		// ajusta relações
		// verifica população de clones/inicial com a população atual
		// para avaliaçãode fitness
		for (Anticorpo anticorpoAComparar : pop) {

			// zera controles de fitness
			anticorpoAComparar.resetControls();

			// avalia reçãode dominância
			relationEvaluate(anticorpoAComparar, pop);

			// marca o fitness mais alto
			if (MAX_FITNESS < anticorpoAComparar.getFitnessValue()) {
				MAX_FITNESS = anticorpoAComparar.getFitnessValue();
				Anticorpo.setMaxFitness(anticorpoAComparar.getFitnessValue());
			}
			if (MIN_FITNESS > anticorpoAComparar.getFitnessValue()) {
				MIN_FITNESS = anticorpoAComparar.getFitnessValue();
				Anticorpo.setMinFitness(anticorpoAComparar.getFitnessValue());
			}
			// marca o omega mais alto
			if (MAX_OMEGA < anticorpoAComparar.getOmegaValue()) {
				MAX_OMEGA = anticorpoAComparar.getOmegaValue();
				Anticorpo.setMaxOmega(anticorpoAComparar.getOmegaValue());
			}
			if (MIN_OMEGA > anticorpoAComparar.getOmegaValue()) {
				MIN_OMEGA = anticorpoAComparar.getOmegaValue();
				Anticorpo.setMinOmega(anticorpoAComparar.getOmegaValue());
			}

		}

		logger.finest("Master reset. MAX_FITNESS." + MAX_FITNESS);
		logger.finest("Master reset. MIN_FITNESS." + MIN_FITNESS);

		logger.finest("Master reset. MAX_OMEGA." + MAX_OMEGA);
		logger.finest("Master reset. MIN_OMEGA." + MIN_OMEGA);

	}

	void cleanMemory(final ArrayList<Anticorpo> memory) {

		// remove os null

		for (Iterator<Anticorpo> iterator = memory.iterator(); iterator
				.hasNext();) {
			Anticorpo anticorpo = iterator.next();

			if (anticorpo == null || anticorpo.getFitnessValue() > 0)
				iterator.remove();

		}
	}

	public void outerLoop(final ArrayList<Anticorpo> pop,
			final ArrayList<Anticorpo> memory) {

		pop.addAll(generatePopulation(nPop));

		// zera contador de problemas
		problema.resetFitnessValue();

		masterReset(pop);

		int gen = 0;
		// loop principal da operação
		do {

			logger.fine("Total Fitness Call:" + problema.getFitnessValue());
			logger.fine("Generation Number:" + gen);

			// loop para a parte interna do algoritmo, clonagem e mutação
			for (int counterIn = 0; counterIn < innerLoops; counterIn++) {

				innerLoop(memory, pop);

				logger.finer("Inner Loop. Counter    : " + counterIn);
				logger.finer("Inner Loop. Memory size: " + memory.size());

			}

			// aplica operadores de afinidade e supressão
			reduce(memory);

			// cleanMemory(memory);
			// substitui população
			pop.clear();

			// adiciona a memória na população
			pop.addAll(memory);

			logger.fine("Memory reduce size: " + memory.size());

			// adiciona novos indivíduos
			if (!problema.stopNow()) {
				// if (gen < 50 - 1) {

				logger.fine("Add random pop. Size: " + nNewPop);

				// tamanho da nova população a ser adicionada
				if ((nPop - pop.size()) < nNewPop)
					pop.addAll(generatePopulation((nPop - pop.size())));
				else
					pop.addAll(generatePopulation(nNewPop));

				memory.clear();

				// reset
				masterReset(pop);

				logger.fine("New pop size: " + pop.size());

			}

			gen++;
		} while (!problema.stopNow());
		// } while (gen < 50);

	}

	void innerLoop(final ArrayList<Anticorpo> memory, final List<Anticorpo> pop) {

		// recupera iterator da população
		for (ListIterator<Anticorpo> popIterator = pop.listIterator(); popIterator
				.hasNext();) {

			final List<Anticorpo> popClone = new ArrayList<Anticorpo>();

			// pega indivíduo
			Anticorpo anticorpo = popIterator.next();

			// gera clones
			popClone.addAll(anticorpo.getEvaluatedClones());

			// avalia fitness de todos os clones
			// entre os clones
			relationsEvaluate(popClone, popClone);

			// avaliação o fitnesse dos clones entre a população
			relationsEvaluate(popClone, pop);

			// avaliação fitness do pai com os clones
			relationEvaluate(anticorpo, popClone);

			// verifica quais clones irão para a memória
			checkMemory(popClone, memory, pop);

			// verifica se o pai pode ir para a memória
			for (Anticorpo mem : memory) {

				checkMemory(anticorpo, mem, pop);
			}

			// adiciona o pai no array de clones.
			// conpara de ID com ID impede que
			// o pai compara ele a ele mesmo e
			// provoque algum tipo de anomalia
			// verifica qual celula vai para a próxima geração
			chooseClone(popIterator, anticorpo, popClone);

			// sempre tenta adicionar o pai
			addMemory(memory, anticorpo);

			if (!popClone.isEmpty())
				addMemory(memory, popClone.toArray(new Anticorpo[0]));

			// limpa
			cleanMemory(memory);

			// reseta total de dominados
			masterReset(pop);

			logger.finer("inner loop current pop size = " + pop.size());

		}
	}

	void checkMemory(final List<Anticorpo> newAg,
			final ArrayList<Anticorpo> memory, final List<Anticorpo> pop) {

		for (Iterator<Anticorpo> iteratorMemory = memory.iterator(); iteratorMemory
				.hasNext();) {

			final Anticorpo memAnticorpo = iteratorMemory.next();

			for (Iterator<Anticorpo> iteratorNewAg = newAg.iterator(); iteratorNewAg
					.hasNext();) {

				final Anticorpo anticorpo = iteratorNewAg.next();

				checkMemory(anticorpo, memAnticorpo, pop);
			}
		}

	}

	void checkMemory(final Anticorpo anticorpo, final Anticorpo memAnticorpo,
			final List<Anticorpo> pop) {

		// compara anticorpo da memória com os candidatos
		switch (LEGENDA.compare(pareto.compare(anticorpo, memAnticorpo))) {
		// memória domina candidato, remova o candidato
		case DOMINA:
			logger.finest("Removendo item:" + memAnticorpo.getID());

			memAnticorpo.addFitness();

			break;
		// memória é dominado
		case DOMINADO:
			// verifica se não está na memória
			// e se nçao está na população
			if (!pop.contains(memAnticorpo)) {
				anticorpo.addFitness();
			}
		}
	}

	void addMemory(final List<Anticorpo> memory, final Anticorpo... anticorpos) {

		for (Anticorpo anticorpo : anticorpos) {

			if (anticorpo.getFitnessValue() == 0) {

				if (memory.size() < nMaxMem) {

					// adiciona o clone na memória
					try {
						memory.add((Anticorpo) anticorpo.clone());
					} catch (Exception e) {
						logger.log(Level.SEVERE, "Erro ao atualizar memória.",
								e);
					}
				}
			}

		}

	}

	void chooseClone(final ListIterator<Anticorpo> popIterator,
			final Anticorpo parent, final List<Anticorpo> anticorpos) {

		// verifica se o pai e pior qe os clones em relaçao ao fitness
		for (Iterator<Anticorpo> proleIterator = anticorpos.iterator(); proleIterator
				.hasNext();) {

			final Anticorpo anticorpoProle = proleIterator.next();

			if (anticorpoProle.getID() != parent.getID()) {

				final int resultCopare = parent.compareTo(anticorpoProle);

				// se a prole for pior q o pai, remove da collection
				if (resultCopare == -1)
					proleIterator.remove();
			}
		}

		if (!anticorpos.isEmpty()) {

			// se tiver mais de uma cara
			// random e pega o primeiro
			if (anticorpos.size() > 1) {
				Collections.shuffle(anticorpos);

				Collections.sort(anticorpos);
			}

			popIterator.set(anticorpos.get(0));
		}

		// ajusta omega de todos os envolvidos
		parent.calculateOmega(anticorpos);

	}

	void relationsEvaluate(final List<Anticorpo> pop,
			final List<Anticorpo> popReferencia) {

		// verifica população de clones/inicial com a população atual
		// para avaliaçãode fitness
		for (final Iterator<Anticorpo> iteratorPop = pop.iterator(); iteratorPop
				.hasNext();) {

			// pega o iterador da população 1 e compara com o resto da
			// população
			final Anticorpo anticorpoAComparar = iteratorPop.next();

			relationEvaluate(anticorpoAComparar, popReferencia);

		}

	}

	void relationEvaluate(final Anticorpo anticorpo,
			final List<Anticorpo> popReferencia) {

		for (int pointer = 0; pointer < popReferencia.size(); pointer++) {
			// pega iterador da população para comparação
			final Anticorpo anticorpoReferencia = popReferencia.get(pointer);

			if (anticorpo.getID() != anticorpoReferencia.getID()) {

				// resultado da avaliação
				final int resultPareto = pareto.compare(anticorpo,
						anticorpoReferencia);

				if (resultPareto == LEGENDA.DOMINADO.valor()) {

					anticorpo.addFitness();

				}
			}
		}

	}

	void reduce(final ArrayList<Anticorpo> memory) {

		final double[][] euclidean_distance = new double[memory.size()][memory
				.size()];

		final double t_thresholdSupression = Math.sqrt(numObjectives)
				/ Double.valueOf(memory.size());

		// calcula distância entro todos os indivíduos
		for (int i = 0; i < memory.size(); i++) {
			for (int j = 0; j < memory.size(); j++) {
				if (i > j)
					euclidean_distance[i][j] = 0;
				else
					euclidean_distance[i][j] = memory.get(i)
							.getEuclideanDistance(memory.get(j));
			}
		}

		final Anticorpo[] memoArray = memory.toArray(new Anticorpo[0]);

		for (int i = 0; i < memoArray.length; i++) {

			for (int j = 0; j < memoArray.length; j++) {

				if ((i < j)
						&& (euclidean_distance[i][j] < t_thresholdSupression)) {

					memory.remove(memoArray[j]);

				}
			}
		}

	}
}
